package com.hmall.search.controller;


import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmall.api.dto.ItemDTO;
import com.hmall.common.domain.PageDTO;
import com.hmall.common.utils.BeanUtils;
import com.hmall.search.domain.po.ItemDoc;
import com.hmall.search.domain.query.ItemPageQuery;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.apache.http.HttpHost;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Api(tags = "搜索相关接口")
@RestController
@RequestMapping("/search")
@RequiredArgsConstructor
public class SearchController {

    private static final Logger log = LoggerFactory.getLogger(SearchController.class);
    private final RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://127.0.0.1:9200")));

    @ApiOperation("根据ID搜索商品")
    @GetMapping("/{id}")
    public ItemDTO searchById(@PathVariable Long id) {
        // 创建Request对象
        GetRequest request = new GetRequest("items", id.toString());
        //发送请求，获取响应数据
        try {
            GetResponse response = client.get(request, RequestOptions.DEFAULT);
            String json = response.getSourceAsString();
            ItemDoc bean = JSONUtil.toBean(json, ItemDoc.class);
            return BeanUtils.copyProperties(bean, ItemDTO.class);
        } catch (IOException e) {
            log.error("调用ES根据ID搜索商品失败,id:{}", id, e);
            return null;
        }
    }

    @ApiOperation("搜索商品")
    @GetMapping("/list")
    public PageDTO<ItemDTO> search(ItemPageQuery query) throws IOException {
        // 搜索
        SearchRequest request = new SearchRequest("items");
        // 组织DSL参数
        // 1.1 准备bool查询
        BoolQueryBuilder bool = QueryBuilders.boolQuery();
        if (StrUtil.isNotBlank(query.getKey())) {
            // 关键字搜索
            bool.must(QueryBuilders.matchQuery("name", query.getKey()));
        }
        if (StrUtil.isNotBlank(query.getCategory())) {
            // 分类过滤
            bool.filter(QueryBuilders.termQuery("category", query.getCategory()));
        }
        if (StrUtil.isNotBlank(query.getBrand())) {
            // 品牌过滤
            bool.filter(QueryBuilders.termQuery("brand", query.getBrand()));
        }
        if (query.getMaxPrice() != null) {
            // 价格最大值过滤
            bool.filter(QueryBuilders.rangeQuery("price").lte(query.getMaxPrice()));
        }
        if (query.getMinPrice() != null) {
            // 价格最小值过滤
            bool.filter(QueryBuilders.rangeQuery("price").gte(query.getMinPrice()));
        }
        request.source().query(bool);
        // 1.2 分页
        request.source().from((query.getPageNo() - 1) * query.getPageSize()).size(query.getPageSize());
        // 对广告字段做一个额外关照
        request.source().query(QueryBuilders.functionScoreQuery(bool,
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                QueryBuilders.termQuery("isAD", true),
                                ScoreFunctionBuilders.weightFactorFunction(100)
                        )
                }).boostMode(CombineFunction.MULTIPLY));
        // 1.3 排序
        if (StrUtil.isNotBlank(query.getSortBy())) {
            request.source().sort(query.getSortBy(), query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC);
        } else {
            request.source().sort("updateTime", query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC);
        }
        // 高亮
        request.source().highlighter(SearchSourceBuilder.highlight()
                .field("name")
                .preTags("<em>")
                .postTags("</em>"));
        // 2. 发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 3. 解析结果
        PageDTO<ItemDTO> pageDTO = parseResponse(response, query);
        return pageDTO;
    }

    private PageDTO<ItemDTO> parseResponse(SearchResponse response, ItemPageQuery query) {
        List<ItemDoc> itemDocList = new ArrayList<>();
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        // 搜索得到的数据
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            // 获取source结果
            String json = hit.getSourceAsString();
            ItemDoc doc = JSONUtil.toBean(json, ItemDoc.class);
            // 解析高亮结果
            Map<String, HighlightField> hfs = hit.getHighlightFields();
            if (CollectionUtil.isNotEmpty(hfs)) {
                // 5.1有高亮结果，获取name的高亮结果
                HighlightField hf = hfs.get("name");
                if (hf != null) {
                    // 5.2 解析高亮结果
                    Text[] fragments = hf.getFragments();
                    if (fragments != null && fragments.length > 0) {
                        // 这里简写了，应该要把这个数组里所有的结构连接成一个字符串
                        doc.setName(fragments[0].toString());
                        // 比如下面
                        String hfName = StrUtil.join("", fragments);
                        doc.setName(hfName);
                    }
                }
            }

            itemDocList.add(doc);
        }
        List<ItemDTO> itemDTOS = BeanUtils.copyToList(itemDocList, ItemDTO.class);
        // 页面大小
        Long pageSize = query.getPageSize().longValue();
        // 返回结果
        PageDTO<ItemDTO> itemDTOPageDTO = new PageDTO<>(total, pageSize, itemDTOS);
        return itemDTOPageDTO;
    }
}